package com.otp.webapp.action.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.ResourceBundle;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;

import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.otp.model.test.instance.TestInstance;
import com.otp.service.util.DOMElementsFromString;
import com.otp.webapp.action.DisplayTestResult;

//@Scope("session")
//@Component("pdfReportManager")
public class PDFReportManager{

	TestInstance testInstance;
	List<DisplayTestResult> data = new ArrayList<DisplayTestResult>();


	public PDFReportManager(TestInstance testInstance, List<DisplayTestResult> data){
		this.testInstance=testInstance;
		this.data=data;
	}
	//TODO Use a testInstanceManager and a constructor that takes only the testInstanceId as String

	public byte[] generatePDF(String dummy){

		ResourceBundle PDFProperties = ResourceBundle.getBundle("PDFReport");
	
		String mainPath = PDFProperties.getString("mainPath");		
		
		String inputFile = PDFProperties.getString("inputXML");
		inputFile = mainPath+inputFile;
		
		String templFile = PDFProperties.getString("templateXSL");
		//Use alternative template file if defined
		String altTemplFile = testInstance.getTestTemplate().getReportTemplate().getPdfTemplateFile();
		if(altTemplFile!=null&&!altTemplFile.isEmpty()) templFile = altTemplFile;
		templFile = mainPath+templFile;


		Document inputDoc = buildXML();
		//(Possibly Commented) method below prints in-memory xml tree to disk
		//Has problems possibly due to the fact that DOM objects are not thread-safe
		//printXMLToFile(inputDoc,inputFile);
		Document templateDoc = readXMLFromFile(templFile);
		return transformToPDF(inputDoc, templateDoc);

	}

	//Build an in-memory XML which will the input to the pdf generation. Optional: save xml file
	public Document buildXML(){

		ResourceBundle PDFProperties = ResourceBundle.getBundle("PDFReport");
		String graphicsPath = PDFProperties.getString("graphicsPath");
		String coverFile = PDFProperties.getString("cover");		
		String logoFile = PDFProperties.getString("logo");
		
		coverFile = graphicsPath+coverFile;
		logoFile = graphicsPath+logoFile;
		

		Document doc = null;
		Element el;

		try{				
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

			doc = docBuilder.newDocument();
			Element rootElement = doc.createElement("doc");
			doc.appendChild(rootElement);

			Element Head = doc.createElement("head");
			rootElement.appendChild(Head);

			Element Body = doc.createElement("body");
			rootElement.appendChild(Body);

			el = doc.createElement("doc_title");
			el.appendChild(doc.createTextNode(testInstance.getTestTemplate().getReportTemplate().getShortDescription()));
			Head.appendChild(el);

			el = doc.createElement("date");
			SimpleDateFormat formatter  = new SimpleDateFormat("dd MMM yyyy");			
			Date date = testInstance.getTestEndDate();
			if(date==null) el.appendChild(doc.createTextNode("No date saved"));
			else el.appendChild(doc.createTextNode(formatter.format(date)));
			Head.appendChild(el);

			el = doc.createElement("company");
			el.appendChild(doc.createTextNode(testInstance.getAppUser().getOrgs().getOrganizationName()));
			Head.appendChild(el);

			el = doc.createElement("logo");
			el.appendChild(doc.createTextNode(logoFile));
			Head.appendChild(el);

			el = doc.createElement("employee");
			el.appendChild(doc.createTextNode(testInstance.getAppUser().getFullName()));
			Head.appendChild(el);

			el = doc.createElement("image");
			el.appendChild(doc.createTextNode(coverFile));
			el.setAttribute("zoom-ratio", "130%");		
			Head.appendChild(el);

			Element Chapter = doc.createElement("chapter");
			Body.appendChild(Chapter);
			Chapter.setAttribute("display-title", "yes");

			el = doc.createElement("chapter_title");
			el.appendChild(doc.createTextNode("Introduction"));
			Chapter.appendChild(el);

			String introduction = testInstance.getTestTemplate().getReportTemplate().getLongDescription();
			//The following command returns a list of pTest elements which may contain nested subP elements
			List<Element> elements=DOMElementsFromString.parseString(introduction, doc);
			for(int i=0; i<elements.size(); i++) Chapter.appendChild(elements.get(i));

			Chapter = doc.createElement("chapter");
			Body.appendChild(Chapter);
			Chapter.setAttribute("display-title", "yes");			

			el = doc.createElement("chapter_title");
			el.appendChild(doc.createTextNode("Overview"));
			Chapter.appendChild(el);

			el = doc.createElement("results_table");
			Chapter.appendChild(el);

			Chapter = doc.createElement("chapter");
			Chapter.setAttribute("display-title", "no");
			Body.appendChild(Chapter);

			el = doc.createElement("chapter_title");
			el.appendChild(doc.createTextNode("Assessment Results"));
			Chapter.appendChild(el);


			for(DisplayTestResult subdata: data)
			{				
				Element Test = doc.createElement("test");
				Chapter.appendChild(Test);

				el = doc.createElement("test_title");
				el.appendChild(doc.createTextNode(subdata.getReportTemplate().getShortDescription().toUpperCase()));
				Test.appendChild(el);

				String shortString = subdata.getReportTemplate().getLongDescription();
				int seperator = shortString.indexOf("Definition: ");
				int seperatorLen = "Definition: ".length();
				if(seperator>=0) shortString = shortString.substring(seperator+seperatorLen);	
				el = doc.createElement("short_description");
				el.appendChild(doc.createTextNode(shortString));
				Test.appendChild(el);


				String longString = subdata.getScoreLongDescription();
				//The following command returns a list of pTest elements which may contain nested subP elements
				elements=DOMElementsFromString.parseString(longString, doc);
				for(int i=0; i<elements.size(); i++) Test.appendChild(elements.get(i));			

				double scoreDec = subdata.getStenScore();
				el = doc.createElement("image");
				el.appendChild(doc.createTextNode(selectImageFile(scoreDec, 0)));
				el.setAttribute("zoom-ratio", "17%");
				Test.appendChild(el);

				el = doc.createElement("imageOverview");
				el.appendChild(doc.createTextNode(selectImageFile(scoreDec, 1)));
				Test.appendChild(el);

				Element Scores = doc.createElement("scores");
				Test.appendChild(Scores);


				if(subdata.getReportTemplate().isShowMaxScore()){
					el = doc.createElement("Max_Score");
					String score = subdata.getMaxScoreString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowRawScore()){
					el = doc.createElement("Raw_Score");
					String score = subdata.getRawScoreString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowPercentageScore()){
					el = doc.createElement("Percentage");
					String score = subdata.getPercentageScoreString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}	

				if(subdata.getReportTemplate().isShowZetaScore()){
					el = doc.createElement("ZScore");
					String score = subdata.getZetaScoreString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}	

				if(subdata.getReportTemplate().isShowPercentileRank()){
					el = doc.createElement("Percentile_Rank");
					String score = subdata.getPercentileRankString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowStenScore()){
					el = doc.createElement("Sten_Score");					
					String score = subdata.getStenScoreString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowThetaEAP()){
					el = doc.createElement("ThetaEAP");
					String score = subdata.getThetaEAPString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowThetaML()){
					el = doc.createElement("ThetaML");
					String score = subdata.getThetaMLString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowThetaMAP()){
					el = doc.createElement("ThetaMAP");
					String score = subdata.getThetaMAPString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowErrorEAP()){
					el = doc.createElement("ErrorEAP");
					String score = subdata.getErrorEAPString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowErrorML()){
					el = doc.createElement("ErrorML");
					String score = subdata.getErrorMLString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}

				if(subdata.getReportTemplate().isShowErrorMAP()){
					el = doc.createElement("ErrorMAP");
					String score = subdata.getErrorMAPString();
					el.appendChild(doc.createTextNode(score));
					Scores.appendChild(el);
				}				
			}

		} catch (ParserConfigurationException pce) {
			pce.printStackTrace();
		} catch (Exception e){
			e.printStackTrace();
		}
		return doc;

	}//end method buildXML

	//Read an XMl file into a Document object
	public static Document readXMLFromFile(String infileName){

		Document doc=null;		 
		try {			 
			File inXML = new File(infileName);
			DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
			dbFactory.setNamespaceAware(true);

			DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
			doc = dBuilder.parse(inXML);

			//optional, but recommended
			doc.getDocumentElement().normalize();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return doc;
	}//end of method readXMLFromFile

	//Print xml Document to file
	public static void printXMLToFile(Document DOMdoc, String outfileName){

		try {
			File outXML = new File(outfileName);

			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer = transformerFactory.newTransformer();

			DOMSource source = new DOMSource(DOMdoc);
			StreamResult result = new StreamResult(outXML.toURI().getPath());
			transformer.transform(source, result); 

		} catch (Exception e) {
			e.printStackTrace();
		}
	}//end of method printXMLToFile

	//Convert DOM document (source) to pdf using DOM document as a template 
	public static byte[] transformToPDF(Document inputDOM, Document templateDOM){

		FopFactory fopFactory = FopFactory.newInstance();

		ByteArrayOutputStream outStream = new ByteArrayOutputStream();


		try {
			Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, outStream);

			Source xslt = new DOMSource(templateDOM);

			TransformerFactory factory = TransformerFactory.newInstance();
			Transformer transformer = factory.newTransformer(xslt);

			Source src = new DOMSource(inputDOM); 

			Result res = new SAXResult(fop.getDefaultHandler());

			transformer.transform(src, res);

		} catch(Exception e){

			e.printStackTrace();

		} finally {
			//Clean-up 
			try {outStream.close();} catch(Exception e) {e.printStackTrace();}
		}
		return outStream.toByteArray();
	}//end method transformToPDF

	//Select the appropriate image based on sten score
	private static String selectImageFile(double score, int imageType){

		//imageType is 0, for miniscores
		//imageType is 1, for bars in overview

		ResourceBundle PDFProperties = ResourceBundle.getBundle("PDFReport");
		String graphicsPath = PDFProperties.getString("graphicsPath");

		switch(imageType){
		case 0: 
			graphicsPath+="miniScores/";
			break;
		case 1:
			graphicsPath+="scoreBars/";
			break;
		default:
			System.out.println("Inappropriate arg in PDFReportManager.selectImageFile()");
		}
		
		String fileName = new String();

		if(score<-5.)			fileName="error.jpg";
		else if(score<1.5) 		fileName="1.png";
		else if(score<2.5)		fileName="2.png";
		else if(score<3.5)		fileName="3.png";
		else if(score<4.5)		fileName="4.png";
		else if(score<5.5)		fileName="5.png";
		else if(score<6.5)		fileName="6.png";
		else if(score<7.5)		fileName="7.png";
		else if(score<8.5)		fileName="8.png";
		else if(score<9.5)		fileName="9.png";
		else if(score<=10.01)	fileName="10.png";
		else					fileName="error.jpg";
		
		return graphicsPath+fileName;
		
	}//end method selectImageFIle

	//Generate a set of strings by a single parent string using standard html <p> and </p> tags
	@SuppressWarnings("unused")
	private static List<String> parseParagraphs(String string){
		String copy = new String();
		copy = string;
		String status="OK";
		int indexStart=0;
		int indexEnd=0;

		List<String> stringParts = new ArrayList<String>();

		while(copy.length()>0){
			copy=copy.trim();
			indexStart=copy.indexOf("<p>");
			indexEnd=copy.indexOf("</p>");

			if(indexStart!=0||indexEnd<=0){
				status="notOK";
				break;
			}

			stringParts.add(copy.substring(3,indexEnd));
			copy=copy.substring(indexEnd+4);
		}

		if (status=="notOK") {stringParts.clear(); stringParts.add(string);}
		return stringParts;
	}//end parseParagraphs method


	
	//Generate a set of strings by a single parent string using standard html <p> and </p> tags
	@SuppressWarnings("unused")
	private static List<String> parseBold(String string){
		String copy = new String();
		copy = string;
		String status="OK";
		int indexStart=0;
		int indexEnd=0;

		List<String> stringParts = new ArrayList<String>();

		while(copy.length()>0){
			copy=copy.trim();
			indexStart=copy.indexOf("<b>");
			indexEnd=copy.indexOf("</b>");

			if(indexStart!=0||indexEnd<=0){
				status="notOK";
				break;
			}

			stringParts.add(copy.substring(3,indexEnd));
			copy=copy.substring(indexEnd+4);
		}

		if (status=="notOK") {stringParts.clear(); stringParts.add(string);}
		return stringParts;
	}//end parseParagraphs method


}

